/**
 *  This file is part of Libgdx by Mario Zechner (badlogicgames@gmail.com)
 *
 *  Libgdx is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Libgdx is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */
package ndquangr.intern.ringtonemaker;

import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
/**
 * A native MP3 {@link Decoder} based on libmad. Decodes
 * stereo input to mono.
 *
 * @author badlogicgames@gmail.com
 *
 */
public class NativeMP3Decoder implements Decoder
{      
		static {
			try {
				System.loadLibrary("mad");
			} catch (UnsatisfiedLinkError e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}	
		}
        /** the handle to the native mp3 decoder **/
        private int handle;
        
        private String filePath;
       
        /** the float buffer used to read in the samples **/
        private FloatBuffer buffer;
       
        /** the short buffer **/
        private ShortBuffer shortBuffer;
        
        /*
         * Header of mp3 file
         * header[0] = samplerate
		 * header[1] = bitrate
		 * header[2] = channels
		 * header[3] = size
         */
        private int [] header;
       
        /**
         * Constructor, sets the file to decode. The file is given in absolute terms! FIXME
         *
         * @param file The file.
         */
        public NativeMP3Decoder( String file )
        {
                handle = openFile( file );
                filePath = file;
                if( handle == -1 ) {
                    throw new IllegalArgumentException( "Couldn't open file '" + file + "'" );
                }                
                header = readHeader(handle);
        }
               
        private native int openFile( String file );
       
        /**
         * Reads in numSamples float PCM samples to the provided direct FloatBuffer using the
         * handle retrievable via {@link NativeMP3Decoder.getHandle()}. This is for
         * people who know what they do. Returns the number of samples actually read.
         *
         * @param handle The handle
         * @param buffer The direct FloatBuffer
         * @param numSamples The number of samples to read
         * @return The number of samples read.
         */
        public native int readSamples( int handle, FloatBuffer buffer, int numSamples );

        /**
         * Reads in numSamples 16-bit signed PCM samples to the provided direct FloatBuffer using the
         * handle retrievable via {@link NativeMP3Decoder.getHandle()}. This is for
         * people who know what they do. Returns the number of samples actually read.
         *
         * @param handle The handle
         * @param buffer The direct FloatBuffer
         * @param numSamples The number of samples to read
         * @return The number of samples read.
         */
        public native int readSamples( int handle, ShortBuffer buffer, int numSamples );
       
        private native void closeFile( int handle );
        
        private native int [] readHeader( int handle);
        
        /**
         * Read cheap sample to show in graph
         * @param handle
         * @param buffer
         * @param length
         * @param step
         */
        public native void readSamples( int handle, ShortBuffer buffer, long length, long step );
        public native int readSamples( int handle, byte[] buffer, int numSamples);
       
        /**
         * @return The handle retrieved from the native side.
         */
        public int getHandle( )
        {
                return handle;
        }
       
        /*
        @Override
        public int readSamples(float[] samples)
        {      
                if( buffer == null || buffer.capacity() != samples.length )
                {
                        ByteBuffer byteBuffer = ByteBuffer.allocateDirect( samples.length * Float.SIZE / 8 );
                        byteBuffer.order(ByteOrder.nativeOrder());
                        buffer = byteBuffer.asFloatBuffer();
                }
               
                int readSamples = readSamples( handle, buffer, samples.length );
                if( readSamples == 0 )
                {
                        closeFile( handle );
                        return 0;
                }
               
                buffer.position(0);
                buffer.get( samples );
               
                return samples.length;
        }
       
        
        @Override
        public int readSamples(short[] samples)
        {      
        	if( shortBuffer == null || shortBuffer.capacity() != samples.length )
            {
                    ByteBuffer byteBuffer = ByteBuffer.allocateDirect( samples.length * Short.SIZE / 8 );
                    byteBuffer.order(ByteOrder.nativeOrder());
                    shortBuffer = byteBuffer.asShortBuffer();
            }
            
            int readSamples = readSamples( handle, shortBuffer, samples.length );
            if( readSamples == 0 )
            {
                    closeFile( handle );
                    return 0;
            }
            
            shortBuffer.position(0);
            shortBuffer.get( samples );
            
            return samples.length;

        }
		*/
        public int readSamples(short[] samples)
        {      
        	if( shortBuffer == null || shortBuffer.capacity() != samples.length )
            {
                    ByteBuffer byteBuffer = ByteBuffer.allocateDirect( samples.length * Short.SIZE / 8 );
                    byteBuffer.order(ByteOrder.nativeOrder());
                    shortBuffer = byteBuffer.asShortBuffer();
            }
            
            int readSamples = readSamples( handle, shortBuffer, samples.length );
            if( readSamples == 0 )
            {
                    //closeFile( handle );
                    return 0;
            }            
            shortBuffer.position(0);
            shortBuffer.get( samples );
            
            return samples.length;

        }
        
        /**
         * {@inheritDoc}
         */
        public void dispose( )
        {
                closeFile(handle);
        }
        
        @Override
        public int readCheapSamples(short [] samples, long step){
        	long buffer_length = samples.length;
        	if( shortBuffer == null || shortBuffer.capacity() != samples.length )
            {
                    ByteBuffer byteBuffer = ByteBuffer.allocateDirect( samples.length * Short.SIZE / 8 );
                    byteBuffer.order(ByteOrder.nativeOrder());
                    shortBuffer = byteBuffer.asShortBuffer();
            }
        	
        	readSamples(handle, shortBuffer, buffer_length, step);
        	shortBuffer.position(0);
            shortBuffer.get(samples );
            
            return samples.length;
        }
        
        @Override
        public int readCheapSamples(short [] samples){
        	//header = readHeader(handle);
        	int step = header[0]/100 - 1;// 10ms
        	long buffer_length = samples.length;
        	if( shortBuffer == null || shortBuffer.capacity() != samples.length )
            {
                    ByteBuffer byteBuffer = ByteBuffer.allocateDirect( samples.length * Short.SIZE / 8 );
                    byteBuffer.order(ByteOrder.nativeOrder());
                    shortBuffer = byteBuffer.asShortBuffer();
            }
        	
        	readSamples(handle, shortBuffer, buffer_length, step);
        	shortBuffer.position(0);
            shortBuffer.get(samples );
            
            return samples.length;
        }
        
        @Override
        public int[] readHeader(){
        	if (handle != -1) {      		
        		int[] header = readHeader(handle);
        		return header;
        	} else {
        		return null;
        	}
        }
        
        @Override
        public boolean saveMP3data(int startTime, int endTime, int totalTime, String newFilePath){
        	closeFile(handle);
        	handle = openFile(filePath);
            if( handle == -1 ) {
                throw new IllegalArgumentException( "Couldn't open file '" + filePath + "'" );
            }                
			if (header[2] == 0) {
				header[2] = 1;
			}
			int bytepersample = 2;
			int format = 1;
			// Get every 10ms data
            int oneMsSecBytes = (header[0]/1000)*header[2]*bytepersample;     
            long skippedSample = (header[2]*header[0]/1000)*startTime;
            long endSample = (header[2]*header[0]/1000)*endTime;
			// Re-calculate data size
			long datasize = (endTime - startTime)*oneMsSecBytes;
			long byterate = header[0]*header[2]*bytepersample;
			int blockalign = header[2]*bytepersample;
			
			int numberOfSampleRead = 2048;
			// Write header
			DataOutputStream outFile;
			try {
				outFile = new DataOutputStream(new FileOutputStream(newFilePath));
			
				outFile.writeBytes("RIFF");				// 00 - RIFF
				outFile.write(WavIO.intToByteArray((int)(datasize+36)), 0, 4);		// 04 - how big is the rest of this file?
				outFile.writeBytes("WAVE");					// 08 - WAVE
				outFile.writeBytes("fmt ");					// 12 - fmt 
				outFile.write(WavIO.intToByteArray((int)16), 0, 4);	// 16 - size of this chunk
				outFile.write(WavIO.shortToByteArray((short)format), 0, 2);		// 20 - what is the audio format? 1 for PCM = Pulse Code Modulation
				outFile.write(WavIO.shortToByteArray((short)header[2]), 0, 2);	// 22 - mono or stereo? 1 or 2?  (or 5 or ???)
				outFile.write(WavIO.intToByteArray((int)header[0]), 0, 4);		// 24 - samples per second (numbers per second)
				outFile.write(WavIO.intToByteArray((int)byterate), 0, 4);		// 28 - bytes per second
				outFile.write(WavIO.shortToByteArray((short)blockalign), 0, 2);	// 32 - # of bytes in one sample, for all channels
				outFile.write(WavIO.shortToByteArray((short)(bytepersample*8)), 0, 2);	// 34 - how many bits in a sample(number)?  usually 16 or 24
				outFile.writeBytes("data");					// 36 - data
				outFile.write(WavIO.intToByteArray((int)datasize), 0, 4);		// 40 - how big is this data chunk				
				
	            if( shortBuffer == null)
	            {
	                    ByteBuffer byteBuffer = ByteBuffer.allocateDirect( (numberOfSampleRead * Short.SIZE / 8) );
	                    byteBuffer.order(ByteOrder.nativeOrder());
	                    shortBuffer = byteBuffer.asShortBuffer();
	            }
	            long readSamples;
	            //long readTime;
	            long readSampleCount = -1057*header[2];
	            long neededSample;
	            while (true) {
		            readSamples = readSamples( handle, shortBuffer, numberOfSampleRead );
		            readSampleCount += readSamples;
		            //readTime = (readSampleCount*1000)/(header[2]*header[0]);
		           
		            if( readSamples == 0 )
		            {
		            	break;
		            } 	
		            
		            if (readSampleCount < skippedSample) {
		            	continue;
		            } else {
		            	if (readSampleCount < (skippedSample + numberOfSampleRead)) {
		            		neededSample = skippedSample + numberOfSampleRead - readSampleCount;
		            		shortBuffer.position((int) (numberOfSampleRead - neededSample));
		            		outFile.write(WavIO.shortToByteArray(shortBuffer, (int) neededSample));
		            		continue;
		            	}
		            }
		            
		            if (readSampleCount > (endSample + readSamples)) {
		            	break;
		            } else if (readSampleCount > endSample) {
		            	neededSample = readSampleCount - endSample;
		            	shortBuffer.position(0);
		            	outFile.write(WavIO.shortToByteArray(shortBuffer, (int) neededSample));
		            	break;
		            }
		            shortBuffer.position(0);
		            outFile.write(WavIO.shortToByteArray(shortBuffer, (int) readSamples));
		            
	            }
	            outFile.flush();
				outFile.close();
				closeFile(handle);
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block				
				e.printStackTrace();
				return false;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}	
                        
        	return true;
        }
        
        public String saveMp3ToTempFile(int totalTime, String rootPath) {
        	if (header[2] == 0) {
				header[2] = 1;
			}
			int bytepersample = 2;
			int format = 1;
			// Calculate data size
			long datasize = (header[2]*header[0]/1000)*totalTime*bytepersample;
			long byterate = header[0]*header[2]*bytepersample;
			int blockalign = header[2]*bytepersample;			
			int numberOfSampleRead = 1024;
			String tempFilePath = rootPath + "/temp.wav";
			// Write header
			DataOutputStream outFile;
			long start = System.currentTimeMillis();
			try {
				outFile = new DataOutputStream(new FileOutputStream(tempFilePath));
			
				outFile.writeBytes("RIFF");				// 00 - RIFF
				outFile.write(WavIO.intToByteArray((int)(datasize+36)), 0, 4);		// 04 - how big is the rest of this file?
				outFile.writeBytes("WAVE");					// 08 - WAVE
				outFile.writeBytes("fmt ");					// 12 - fmt 
				outFile.write(WavIO.intToByteArray((int)16), 0, 4);	// 16 - size of this chunk
				outFile.write(WavIO.shortToByteArray((short)format), 0, 2);		// 20 - what is the audio format? 1 for PCM = Pulse Code Modulation
				outFile.write(WavIO.shortToByteArray((short)header[2]), 0, 2);	// 22 - mono or stereo? 1 or 2?  (or 5 or ???)
				outFile.write(WavIO.intToByteArray((int)header[0]), 0, 4);		// 24 - samples per second (numbers per second)
				outFile.write(WavIO.intToByteArray((int)byterate), 0, 4);		// 28 - bytes per second
				outFile.write(WavIO.shortToByteArray((short)blockalign), 0, 2);	// 32 - # of bytes in one sample, for all channels
				outFile.write(WavIO.shortToByteArray((short)(bytepersample*8)), 0, 2);	// 34 - how many bits in a sample(number)?  usually 16 or 24
				outFile.writeBytes("data");					// 36 - data
				outFile.write(WavIO.intToByteArray((int)datasize), 0, 4);		// 40 - how big is this data chunk				
				
				byte [] bytebuffer = new byte [numberOfSampleRead];
				long readSamples;
	            long readSampleCount = 0;
				while (true) {
		            readSamples = readSamples( handle, bytebuffer, numberOfSampleRead );
		            readSampleCount += readSamples;
		           
		            if( readSamples == 0 )
		            {
		            	break;
		            } 	
		            
		            //shortBuffer.position(0);
		            //WavIO.shortToByteArray(shortBuffer, (int) readSamples);
		            //outFile.write(bytebuffer);
		            
	            }
	            outFile.flush();
				outFile.close();
				closeFile(handle);
				long end = System.currentTimeMillis();
				long time = end - start;
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block				
				e.printStackTrace();
				return null;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			}	
        	return tempFilePath;
        }
}

